﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.Serialization;
using System.ServiceModel;
using System.Text;
using Celeriq.Common;
using Celeriq.Server.Interfaces;
using Celeriq.Utilities;
using System.IO;
using Celeriq.DataCore.EFDAL;
using System.Threading.Tasks;
using System.Diagnostics;

namespace Celeriq.Server.Core
{
    [Serializable]
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class RepositoryManager : Celeriq.Common.IDataModel
    {
        private Celeriq.Client.Interfaces.ISystemCore _system = null;
        private Dictionary<Guid, RemotingObjectCache> _repositoryList = new Dictionary<Guid, RemotingObjectCache>();

        public RepositoryManager(Celeriq.Client.Interfaces.ISystemCore system)
        {
            _system = system;
        }

        public void Initialize()
        {
            try
            {
                lock (_repositoryList)
                {
                    var timer = new Stopwatch();
                    timer.Start();

                    //Load all repositories into memory
                    using (var context = new DataCoreEntities())
                    {
                        var masterKeys = ConfigHelper.MasterKeys;
                        var list = context.RepositoryDefinition.OrderBy(x => x.RepositoryId).ToList();
                        foreach (var item in list)
                        {
                            var newItem = new RemotingObjectCache();
                            newItem.DataDiskSize = 0;
                            newItem.DataMemorySize = 0;
                            newItem.IsLoaded = false;
                            newItem.ItemCount = item.ItemCount;
                            newItem.LastUnloadTime = null;
                            newItem.VersionHash = item.VersionHash;
                            newItem.Repository = item.DefinitionData.BinToObject<RepositorySchema>();
                            newItem.Repository.ID = item.UniqueKey;

                            var repository = new Celeriq.RepositoryAPI.Repository(item.RepositoryId, newItem.Repository, masterKeys, _system);
                            newItem.ServiceInstance = repository;
                            _repositoryList.Add(item.UniqueKey, newItem);
                        }
                    }

                    timer.Stop();
                    Logger.LogInfo("Core Started: Elapsed: " + timer.ElapsedMilliseconds);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public void RemoveRepository(Guid repositoryId)
        {
            try
            {
                Celeriq.DataCore.EFDAL.Entity.DimensionData.DeleteData(x => x.DimensionStore.RepositoryDefinition.UniqueKey == repositoryId);
                Celeriq.DataCore.EFDAL.Entity.DimensionStore.DeleteData(x => x.RepositoryDefinition.UniqueKey == repositoryId);
                Celeriq.DataCore.EFDAL.Entity.RepositoryData.DeleteData(x => x.RepositoryDefinition.UniqueKey == repositoryId);
                Celeriq.DataCore.EFDAL.Entity.RepositoryStat.DeleteData(x => x.RepositoryDefinition.UniqueKey == repositoryId);
                Celeriq.DataCore.EFDAL.Entity.RepositoryLog.DeleteData(x => x.RepositoryDefinition.UniqueKey == repositoryId);
                Celeriq.DataCore.EFDAL.Entity.RepositoryDefinition.DeleteData(x => x.UniqueKey == repositoryId);

                lock (_repositoryList)
                {
                    _repositoryList.Remove(repositoryId);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public bool Contains(Guid repositoryId)
        {
            lock (_repositoryList)
            {
                return _repositoryList.ContainsKey(repositoryId);
            }
        }

        public IEnumerable<RemotingObjectCache> List
        {
            get
            {
                lock (_repositoryList)
                {
                    return _repositoryList.Values;
                }
            }
        }

        public Celeriq.Server.Interfaces.IRepository AddRepository(Guid repositoryId, RepositorySchema schema)
        {
            try
            {
                lock (_repositoryList)
                {
                    if (_repositoryList.ContainsKey(repositoryId))
                    {
                        _repositoryList.Remove(repositoryId);
                    }

                    using (var context = new Celeriq.DataCore.EFDAL.DataCoreEntities())
                    {
                        var item = context.RepositoryDefinition.FirstOrDefault(x => x.UniqueKey == repositoryId);
                        if (item != null)
                        {
                            //item.DimensionStoreList.SelectMany(x => x.DimensionDataList).ToList().Clear();
                            //item.DimensionStoreList.Clear();
                            //item.RepositoryLogList.Clear();
                            //item.RepositoryStatList.Clear();
                            item.RepositoryDataList.ToList().ForEach(x => context.DeleteItem(x));
                            context.SaveChanges();
                        }
                        else
                        {
                            item = new Celeriq.DataCore.EFDAL.Entity.RepositoryDefinition();
                            item.UniqueKey = repositoryId;
                            context.AddItem(item);
                        }

                        item.Name = schema.Name;
                        item.ItemCount = 0;
                        item.MemorySize = 0;
                        item.ItemCount = 0;
                        item.VersionHash = schema.VersionHash;
                        item.DefinitionData = schema.ObjectToBin();
                        var c = context.SaveChanges();

                        var repository = new Celeriq.RepositoryAPI.Repository(item.RepositoryId, schema, ConfigHelper.MasterKeys, _system);
                        var remoteItem = new RemotingObjectCache() {ServiceInstance = repository, Repository = schema};
                        remoteItem.DataDiskSize = 0;
                        remoteItem.DataMemorySize = 0;
                        remoteItem.IsLoaded = false;
                        remoteItem.ItemCount = 0;
                        remoteItem.LastUnloadTime = null;
                        remoteItem.VersionHash = schema.VersionHash;
                        remoteItem.Repository = schema;
                        remoteItem.VersionHash = schema.VersionHash;

                        _repositoryList.Add(repositoryId, remoteItem);
                    }

                    if (_repositoryList.ContainsKey(repositoryId))
                        return _repositoryList[repositoryId].ServiceInstance;
                    return null;
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public DateTime GetLastAccess(Guid repositoryId)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                return repository.ServiceInstance.LastAccess;

            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public void UnloadData(Guid repositoryId)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                repository.ServiceInstance.UnloadData();
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public void LoadData(Guid repositoryId, UserCredentials credentials)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                repository.ServiceInstance.Query(new DataQuery() { Credentials = credentials });
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
            }
        }

        public string[] UpdateData(Guid repositoryId, IEnumerable<DataItem> list, UserCredentials credentials)
        {
            var errorList = new List<string>();
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                    errorList.Add("The repository has not been initialized!");
                else
                    repository.ServiceInstance.UpdateIndexList(list, credentials);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                errorList.Add(ex.ToString());
            }
            return errorList.ToArray();
        }

        public string[] DeleteData(Guid repositoryId, IEnumerable<DataItem> item, UserCredentials credentials)
        {
            var errorList = new List<string>();
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                    errorList.Add("The repository has not been initialized!");
                else
                    repository.ServiceInstance.DeleteData(item, credentials);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                errorList.Add(ex.ToString());
            }
            return errorList.ToArray();
        }

        public string[] Clear(Guid repositoryId, UserCredentials credentials)
        {
            var errorList = new List<string>();
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                {
                    errorList.Add("The repository has not been initialized!");
                }
                else
                {
                    repository.ServiceInstance.Clear(credentials);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                errorList.Add(ex.ToString());
            }
            return errorList.ToArray();
        }

        public DataQueryResults Query(Guid repositoryId, DataQuery query)
        {
            var retval = new DataQueryResults();
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                {
                    retval.ErrorList = new string[] { "The repository has not been initialized!" };
                }
                else
                {
                    retval = repository.ServiceInstance.Query(query);
                }
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                retval.ErrorList = new string[] { ex.ToString() };
            }
            return retval;
        }

        public void ShutDown(Guid repositoryId)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                    throw new Exception("The repository has not been initialized!");

                repository.ServiceInstance.ShutDown();
                repository = null;

            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        /// <summary>
        /// Copies the entire repository to an archive file
        /// </summary>
        public bool ExportSchema(Guid repositoryId, UserCredentials credentials, string backupFile)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                return repository.ServiceInstance.ExportSchema(credentials, backupFile);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        /// <summary>
        /// Copies the entire repository to an archive file
        /// </summary>
        public bool Backup(Guid repositoryId, UserCredentials credentials, string backupFile)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                return repository.ServiceInstance.Backup(credentials, backupFile);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        public bool Restore(Guid repositoryId, UserCredentials credentials, string backupFile)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                return repository.ServiceInstance.Restore(credentials, backupFile);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        /// <summary>
        /// Returns the size on disk of the repository data
        /// </summary>
        /// <returns></returns>
        public long GetDataDiskSize(Guid repositoryId, UserCredentials credentials)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null) return 0;
                return repository.ServiceInstance.GetDataDiskSize(credentials);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        /// <summary>
        /// Returns the size of memory of the repository data
        /// </summary>
        /// <returns></returns>
        public long GetDataMemorySize(Guid repositoryId, UserCredentials credentials)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null) return 0;
                return repository.ServiceInstance.GetDataMemorySize(credentials);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        /// <summary>
        /// Returns the number of items in the repository
        /// </summary>
        /// <returns></returns>
        public long GetItemCount(Guid repositoryId, UserCredentials credentials)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null) return 0;
                return repository.ServiceInstance.GetItemCount(credentials);
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        //public void Compress(Guid repositoryId, UserCredentials credentials)
        //{
        //    try
        //    {
        //        //EnsureRepositoryLoaded(repositoryId);
        //        var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
        //        if (repository == null)
        //            throw new Exception("The repository has not been initialized!");

        //        //repository.ServiceInstance.Compress(credentials);
        //    }
        //    catch (Exception ex)
        //    {
        //        Logger.LogError(ex);
        //        throw;
        //    }
        //}

        public bool IsLoaded(Guid repositoryId)
        {
            try
            {
                //EnsureRepositoryLoaded(repositoryId);
                var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
                if (repository == null)
                    return false;
                else
                    return repository.IsLoaded;
            }
            catch (Exception ex)
            {
                Logger.LogError(ex);
                throw;
            }
        }

        //public ProfileItem[] GetProfile(Guid repositoryId, UserCredentials credentials, long lastProfileId)
        //{
        //    try
        //    {
        //        var repository = this.List.FirstOrDefault(x => x.Repository.ID == repositoryId);
        //        return repository.ServiceInstance.GetProfile(credentials, lastProfileId);
        //    }
        //    catch (Exception ex)
        //    {
        //        Logger.LogError(ex);
        //        throw;
        //    }
        //}

    }
}